﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;

namespace iNet.Media
{
	/// <summary>
	/// 以多個 IMetadata 介面為資料來源的中繼資料。
	/// </summary>
	public class HybridMetadata : IMetadata
	{
		#region GetPropertyCallback
		/// <summary>
		/// 提供從來源 IMetadata 介面取得屬性值的回呼方法。
		/// </summary>
		/// <typeparam name="T">屬性值的型別。</typeparam>
		/// <param name="source">來源 IMetadata 介面。</param>
		/// <param name="userData">使用者自訂參數。</param>
		/// <returns>取得的屬性值。</returns>
		/// <exception cref="NotSupportedException">屬性不支援。</exception>
		protected delegate T GetPropertyCallback<T>(IMetadata source, object userData);
		#endregion
		#region PropertyUpdateMode
		/// <summary>
		/// 表示中繼資料屬性值更新方式。
		/// </summary>
		public enum PropertyUpdateMode
		{
			/// <summary>
			/// 更新至所有 IMetadata 介面。
			/// </summary>
			All,
			/// <summary>
			/// 更新至第一個可更新的 IMetadata 介面。
			/// </summary>
			FirstAvailable,
		}
		#endregion
		#region SetPropertyCallback
		/// <summary>
		/// 提供更新來源 IMetadata 介面屬性值的回呼方法。
		/// </summary>
		/// <typeparam name="T">屬性值的型別。</typeparam>
		/// <param name="target">來源 IMetadata 介面。</param>
		/// <param name="value">屬性值。</param>
		/// <param name="userData">使用者自訂參數。</param>
		protected delegate void SetPropertyCallback<T>(IMetadata target, T value, object userData);
		#endregion	
		#region 欄位
		bool _IsReadOnly;
		ReadOnlyCollection<IMetadata> _SourceList;
		IMetadata[] _Sources;
		PropertyUpdateMode _UpdateMode;
		#endregion
		#region 建構子
		/// <summary>
		/// 初始化新的 HybridMetadata 執行個體。
		/// </summary>
		/// <param name="sources">做為資料來源的 IMetadata 介面列表。</param>
		/// <param name="template">範本中繼資料。</param>
		/// <param name="isReadOnly">是否為唯讀。</param>
		/// <param name="updateMode">中繼資料屬性值更新方式，預設值為 PropertyUpdateMode.All。</param>
		public HybridMetadata(IList<IMetadata> sources, IMetadata template, bool isReadOnly, PropertyUpdateMode updateMode = PropertyUpdateMode.All)
		{
			//儲存來源
			if (sources != null)
				_Sources = sources.ToArray<IMetadata>();
			else
				_Sources = new IMetadata[0];

			//建立來源清單
			_SourceList = new ReadOnlyCollection<IMetadata>(_Sources);

			//初始化屬性值
			if (template != null)
				this.CopyFrom(template);

			//初始化其他欄位
			_IsReadOnly = isReadOnly;
		}
		#endregion
		#region Authors
		/// <summary>
		/// 取得或設定媒體的作者。
		/// </summary>
		public string[] Authors
		{
			get
			{
				return this.GetProperty<string[]>((source, userData) =>
				{
					return source.Authors;
				});
			}
			set
			{
				this.SetProperty<string[]>((target, newValue, userData) =>
				{
					target.Authors = newValue;
				}, value);
			}
		}
		#endregion
		#region City
		/// <summary>
		/// 取得或設定媒體內容所在的城市。
		/// </summary>
		public string City
		{
			get
			{
				return this.GetProperty<string>((source, userData) =>
				{
					return source.City;
				});
			}
			set
			{
				this.SetProperty<string>((target, newValue, userData) =>
				{
					target.City = newValue;
				}, value);
			}
		}
		#endregion
		#region Clone
		/// <summary>
		/// 建立可修改的 HybridMetadata 副本。
		/// </summary>
		/// <returns>HybridMetadata 副本。</returns>
		public virtual object Clone()
		{
			return new HybridMetadata(_Sources, null, false, _UpdateMode);
		}
		#endregion
		#region Comment
		/// <summary>
		/// 取得或設定媒體的註解文字。
		/// </summary>
		public string Comment
		{
			get
			{
				return this.GetProperty<string>((source, userData) =>
				{
					return source.Comment;
				});
			}
			set
			{
				this.SetProperty<string>((target, newValue, userData) =>
				{
					target.Comment = newValue;
				}, value);
			}
		}
		#endregion
		#region CopyFrom
		/// <summary>
		/// 從指定的中繼資料複製屬性。
		/// </summary>
		/// <param name="metadata">指定的中繼資料。</param>
		/// <exception cref="InvalidOperationException">中繼資料為唯讀。</exception>
		public virtual void CopyFrom(IMetadata metadata)
		{
			//確認狀態
			if (this.IsReadOnly)
				throw new InvalidOperationException();

			//複製
			if (metadata != null && metadata != this)
			{
				for (int i = _SourceList.Count - 1; i >= 0; --i)
				{
					IMetadata source = _SourceList[i];
					if (source != null)
						source.CopyFrom(metadata);
				}
			}
		}
		#endregion
		#region Copyright
		/// <summary>
		/// 取得或設定媒體的版權資訊。
		/// </summary>
		public string Copyright
		{
			get
			{
				return this.GetProperty<string>((source, userData) =>
				{
					return source.Copyright;
				});
			}
			set
			{
				this.SetProperty<string>((target, newValue, userData) =>
				{
					target.Copyright = newValue;
				}, value);
			}
		}
		#endregion
		#region CountryRegion
		/// <summary>
		/// 取得或設定媒體內容所在的國家或地區。
		/// </summary>
		public string CountryRegion
		{
			get
			{
				return this.GetProperty<string>((source, userData) =>
				{
					return source.CountryRegion;
				});
			}
			set
			{
				this.SetProperty<string>((target, newValue, userData) =>
				{
					target.CountryRegion = newValue;
				}, value);
			}
		}
		#endregion
		#region CreationTime
		/// <summary>
		/// 取得或設定媒體建立時間。
		/// </summary>
		public DateTime? CreationTime
		{
			get
			{
				return this.GetProperty<DateTime?>((source, userData) =>
				{
					return source.CreationTime;
				});
			}
			set
			{
				this.SetProperty<DateTime?>((target, newValue, userData) =>
				{
					target.CreationTime = newValue;
				}, value);
			}
		}
		#endregion
		#region GetAsReadOnly
		/// <summary>
		/// 取得唯讀中繼資料。
		/// </summary>
		/// <returns>唯讀中繼資料。</returns>
		public virtual IMetadata GetAsReadOnly()
		{
			if (_IsReadOnly)
				return this;
			return new HybridMetadata(_Sources, null, true, _UpdateMode);
		}
		#endregion
		#region GetProperty
		/// <summary>
		/// 依照來源 IMetadata 介面的順序，取得第一個可用的屬性值。
		/// </summary>
		/// <typeparam name="T">屬性值的型別。</typeparam>
		/// <param name="callback">提供從來源 IMetadata 介面取得屬性值的回呼方法。</param>
		/// <param name="userData">使用者自訂參數。</param>
		/// <returns>取得的屬性值。若無法取得屬性值，則回傳型別預設值。</returns>
		/// <exception cref="ArgumentNullException">callback 為 Null (Visual Basic 為 Nothing)。</exception>
		protected T GetProperty<T>(GetPropertyCallback<T> callback, object userData = null)
		{
			//確認參數
			if (callback == null)
				throw new ArgumentNullException("callback");

			//取得屬性值。
			bool isValueType = typeof(T).IsValueType;
			T defaultValue = default(T);
			for (int i = 0; i < _Sources.Length; ++i)
			{
				T value = callback(_Sources[i], userData);
				if ((isValueType && !value.Equals(defaultValue)) || (!isValueType && value != null))
					return value;
			}
			return defaultValue;
		}
		#endregion
		#region IsReadOnly
		/// <summary>
		/// 取得中繼資料是否為唯讀。
		/// </summary>
		public bool IsReadOnly
		{
			get 
			{
				return _IsReadOnly;
			}
		}
		#endregion
		#region Keywords
		/// <summary>
		/// 取得或設定媒體的關鍵字。
		/// </summary>
		public string[] Keywords
		{
			get
			{
				return this.GetProperty<string[]>((source, userData) =>
				{
					return source.Keywords;
				});
			}
			set
			{
				this.SetProperty<string[]>((target, newValue, userData) =>
				{
					target.Keywords = newValue;
				}, value);
			}
		}
		#endregion
		#region LastWriteTime
		/// <summary>
		/// 取得或設定媒體最近更新時間。
		/// </summary>
		public DateTime? LastWriteTime
		{
			get
			{
				return this.GetProperty<DateTime?>((source, userData) =>
				{
					return source.LastWriteTime;
				});
			}
			set
			{
				this.SetProperty<DateTime?>((target, newValue, userData) =>
				{
					target.LastWriteTime = newValue;
				}, value);
			}
		}
		#endregion
		#region Rating
		/// <summary>
		/// 取得或設定媒體的評價。
		/// </summary>
		public int? Rating
		{
			get
			{
				return this.GetProperty<int?>((source, userData) =>
				{
					return source.Rating;
				});
			}
			set
			{
				this.SetProperty<int?>((target, newValue, userData) =>
				{
					target.Rating = newValue;
				}, value);
			}
		}
		#endregion
		#region RatingPercentage
		/// <summary>
		/// 取得或設定媒體的評價百分比。
		/// </summary>
		public int? RatingPercentage
		{
			get
			{
				return this.GetProperty<int?>((source, userData) =>
				{
					return source.RatingPercentage;
				});
			}
			set
			{
				this.SetProperty<int?>((target, newValue, userData) =>
				{
					target.RatingPercentage = newValue;
				}, value);
			}
		}
		#endregion
		#region SetProperty
		/// <summary>
		/// 更新來源 IMetadata 介面中的屬性值。
		/// </summary>
		/// <typeparam name="T">屬性值的型別。</typeparam>
		/// <param name="callback">提供更新來源 IMetadata 介面屬性值的回呼方法。</param>
		/// <param name="value">新的屬性值。</param>
		/// <param name="userData">使用者自訂參數。</param>
		/// <exception cref="ArgumentNullException">callback 為 Null (Visual Basic 為 Nothing)。</exception>
		/// <exception cref="InvalidOperationException">中繼資料為唯讀。</exception>
		/// <exception cref="NotSupportedException">屬性不支援。</exception>
		protected void SetProperty<T>(SetPropertyCallback<T> callback, T value, object userData = null)
		{
			//確認參數
			if (callback == null)
				throw new ArgumentNullException("callback");

			//確認狀態
			if (_IsReadOnly)
				throw new InvalidOperationException();

			//確認來源
			if (_Sources.Length == 0)
				return;

			//更新中繼資料
			bool updated = false;
			for (int i = _Sources.Length - 1; i >= 0; --i)
			{
				try
				{
					callback(_Sources[i], value, userData);
					if (_UpdateMode == PropertyUpdateMode.FirstAvailable)
						return;
					updated = true;
				}
				catch(NotSupportedException ex)
				{ }
			}
			if (!updated)
				throw new NotSupportedException();
		}
		#endregion
		#region Software
		/// <summary>
		/// 取得或設定製作媒體的軟體名稱。
		/// </summary>
		public string Software
		{
			get
			{
				return this.GetProperty<string>((source, userData) =>
				{
					return source.Software;
				});
			}
			set
			{
				this.SetProperty<string>((target, newValue, userData) =>
				{
					target.Software = newValue;
				}, value);
			}
		}
		#endregion
		#region Sources
		/// <summary>
		/// 取得做為資料來源的 IMetadata 介面列表。
		/// </summary>
		public ReadOnlyCollection<IMetadata> Sources
		{
			get
			{
				return _SourceList;
			}
		}
		#endregion
		#region StateProvince
		/// <summary>
		/// 取得或設定媒體內容所在的省或州。
		/// </summary>
		public string StateProvince
		{
			get
			{
				return this.GetProperty<string>((source, userData) =>
				{
					return source.StateProvince;
				});
			}
			set
			{
				this.SetProperty<string>((target, newValue, userData) =>
				{
					target.StateProvince = newValue;
				}, value);
			}
		}
		#endregion
		#region Subject
		/// <summary>
		/// 取得或設定媒體的主旨。
		/// </summary>
		public string Subject
		{
			get
			{
				return this.GetProperty<string>((source, userData) =>
				{
					return source.Subject;
				});
			}
			set
			{
				this.SetProperty<string>((target, newValue, userData) =>
				{
					target.Subject = newValue;
				}, value);
			}
		}
		#endregion
		#region Title
		/// <summary>
		/// 取得或設定媒體的標題。
		/// </summary>
		public string Title
		{
			get
			{
				return this.GetProperty<string>((source, userData) =>
				{
					return source.Title;
				});
			}
			set
			{
				this.SetProperty<string>((target, newValue, userData) =>
				{
					target.Title = newValue;
				}, value);
			}
		}
		#endregion
		#region UpdateMode
		/// <summary>
		/// 取得中繼資料屬性值更新方式。
		/// </summary>
		public PropertyUpdateMode UpdateMode
		{
			get
			{
				return _UpdateMode;
			}
		}
		#endregion
	}
}
